package com.home.auto_navigation.common;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.google.common.base.Preconditions;
import com.home.auto_navigation.common.api.NavigationLink;
import com.home.auto_navigation.ui.AbstractPageObject;
import com.home.auto_navigation.ui.pages.HomePage;

public class SiteMap {

	private static SiteMap siteMap;
	private Map<Class<? extends AbstractPageObject>, List<Class<? extends AbstractPageObject>>> map;
	private Class<? extends AbstractPageObject> startPage = HomePage.class; 
    
	private SiteMap() {
		//generateSiteMap();
		createMap(HomePage.class);
	}

	public static SiteMap getInstance(){
		if(siteMap != null){
			return siteMap;
		} else {
			return siteMap = new SiteMap();
		}		
	}
	
	
	//old method, used map successors which was filled in constructor
	private void generateSiteMap() {
			Class<? extends AbstractPageObject> pageStart = getStartPage();
			map = new HashMap<>();
			List<Class<? extends AbstractPageObject>> used = new ArrayList<>();
			LinkedList<Class<? extends AbstractPageObject>> currentlyPages = new LinkedList<>();
			try {
				AbstractPageObject pageStartInstance = pageStart.newInstance();
				map.put(pageStart, pageStartInstance
						.getSuccessorKey(pageStartInstance.successors));
				used.add(pageStart);
				currentlyPages.add(pageStart);
				while (!currentlyPages.isEmpty()) {
					Class<? extends AbstractPageObject> page = currentlyPages
							.getLast();
					currentlyPages.removeLast();
					AbstractPageObject pageInstance = page.newInstance();
					if (pageInstance.successors != null) {
						List<Class<? extends AbstractPageObject>> listPages = pageInstance
								.getSuccessorKey(pageInstance.successors);
						for (int i = 0; i < listPages.size(); ++i) {
							Class<? extends AbstractPageObject> pageTo = listPages
									.get(i);
							AbstractPageObject pageToInstance = pageTo.newInstance();
							if (!used.contains(pageTo)) {
								used.add(pageTo);
								currentlyPages.add(pageTo);
								map.put(pageTo, pageToInstance
												.getSuccessorKey(pageToInstance.successors));
							}
						}
					}
				}
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
	}

	public Map<Class<? extends AbstractPageObject>, List<Class<? extends AbstractPageObject>>> getMap() {
		return map;
	}
	
	private void createMap(Class<? extends AbstractPageObject> startPage){
	    Preconditions.checkArgument(startPage != null, "start page should be setted");
	    
		map = new HashMap<>();
		List<Class<? extends AbstractPageObject>> used = new ArrayList<>();
		Deque<Class<? extends AbstractPageObject>> currentlyPages = new LinkedList<>();
		List<Class<? extends AbstractPageObject>> pageSuccessors = null;
		pageSuccessors = new LinkedList<>();
		for(Field field: startPage.getDeclaredFields()){
			if(field.isAnnotationPresent(NavigationLink.class)){
				pageSuccessors.add(field.getAnnotation(NavigationLink.class).successor());
			}
		}
		map.put(startPage, pageSuccessors);
		used.add(startPage);
		currentlyPages.add(startPage);
		while(!currentlyPages.isEmpty()){
			startPage = currentlyPages.getLast();
			currentlyPages.removeLast();
			List<Class<? extends AbstractPageObject>> listPages = new LinkedList<>();
			for(Field field: startPage.getDeclaredFields()){
				if(field.isAnnotationPresent(NavigationLink.class)){
					listPages.add(field.getAnnotation(NavigationLink.class).successor());
				}
			}
			for (int i = 0; i < listPages.size(); ++i) {
				startPage = listPages.get(i);
				if (!used.contains(startPage)) {
					used.add(startPage);
					currentlyPages.add(startPage);
					pageSuccessors = new LinkedList<Class<? extends AbstractPageObject>>();
					for(Field field: startPage.getDeclaredFields()){
						if(field.isAnnotationPresent(NavigationLink.class)){
							pageSuccessors.add(field.getAnnotation(NavigationLink.class).successor());
						}
					}
					map.put(startPage, pageSuccessors);
				}
			}		
		}
	}
		   
    public Class<? extends AbstractPageObject> getStartPage() {
        return startPage;
    }

    public void setStartPage(Class<? extends AbstractPageObject> startPage) {
        this.startPage = startPage;
    }

}
